﻿#include "pch.h"
#if _QG_ENABLE_WIDGET
#include "qgp/qgach.h"
#include "qgp/qgaud.h"
#include "qgp/qguwgs.h"

//////////////////////////////////////////////////////////////////////////
// 위젯

namespace _widget
{
	// 텍스쳐
	extern qgTexture* CreateUiTexture();

	// 소트 클래스
	struct SortWidget
	{
		bool operator()(qwCtrl* l, qwCtrl* r)
		{
			if (l->GetType() == QGW_FORM)
			{
				if (r->GetType() != QGW_FORM)
					return true;
				else
					return l->GetDepth() > r->GetDepth();
			}
			else
			{
				if (r->GetType() == QGW_FORM)
					return false;
				else
					return l->GetDepth() > r->GetDepth();
			}
		}

		// k_qsort용 콜백
		static kint Callback(kconstpointer p1, kconstpointer p2)
		{
			qwCtrl* w1 = *(qwCtrl**)p1;
			qwCtrl* w2 = *(qwCtrl**)p2;

			if (w1->GetDepth() > w2->GetDepth())
				return -1;
			else if (w1->GetDepth() < w2->GetDepth())
				return 1;
			else
				return 0;
		}
	};
}


//////////////////////////////////////////////////////////////////////////
// 컨트롤
KIMPL_SUBCLASS_ABSTRACT(qwCtrl, "WidgetControl");

qwCtrl::qwCtrl(qgWidgetType type)
	: _type(type)
	, _bound(0, 0, 50, 50)
	, _status(QGWS_NORMAL)
	, _refresh(true)
	, _color_shadow(30, 30, 30, 128)
	, _color_border(255, 255, 255)
{
	_insts.enable = true;
	_insts.visible = true;

	_text_info.size = 16;
	_text_info.align = QGWALIGN_CENTER | QGWALIGN_VCENTER;
	_text_info.color.Set(255, 255, 255);
	_text_info.anch.Set(0, 0);

	// OnInitialize 참조
}

qwCtrl::~qwCtrl()
{
}

const qbWidgetResource& qwCtrl::GetResource(qgWidgetElement nth)
{
	if (nth >= QGWELM_NORMAL && nth <= QGWELM_DISABLE)
		return _res[nth];
	else
	{
		static qbWidgetResource DummyResource =
		{
			tpl::ColorValue(0xFFFFFFFF),
			tpl::Quad(0.0f, 0.0f, 0.0f, 0.0f),
			false
		};

		return DummyResource;
	}
}

bool qwCtrl::SetResource(qgWidgetElement nth, const qbWidgetResource& res)
{
	k_return_value_if_fail((ksize_t)nth < (ksize_t)QGWS_MAX_VALUE, false);

	_res[nth] = res;

	return true;
}

void qwCtrl::OpenTexture(const char* filename)
{
	_tex.InitPtr(qgRdh::Instance->CreateTexture(filename, QGTEXF_NOMIP));
}

void qwCtrl::SetTextSize(kint size)
{
	_text_info.size = size;
	SetRefresh();
}

void qwCtrl::SetTextAlign(kint align)
{
	_text_info.align = align;
	SetRefresh();
}

void qwCtrl::SetTextColor(const tpl::ColorValue& color)
{
	_text_info.color = color;
	SetRefresh();
}

bool qwCtrl::ReplaceColor(qgWidgetElement nth, const tpl::ColorValue& value)
{
	qbWidgetResource res = GetResource(nth);
	res.color = value;
	return SetResource(nth, res);
}

void qwCtrl::SetBound(const tpl::Rect& bound)
{
	_bound = bound;
	SetRefresh();
}

void qwCtrl::SetBound(kint x, kint y, kint w, kint h)
{
	_bound.SetSize(x, y, w, h);
	SetRefresh();
}

void qwCtrl::SetLocation(kint x, kint y)
{
	_bound.Move(x, y);
	SetRefresh();
}

void qwCtrl::SetSize(kint w, kint h)
{
	_bound.Expand(w, h);
	SetRefresh();
}

void qwCtrl::SetStatus(qgWidgetStatus status)
{
	OnChangeStatus(status);
	_status = status;
}

void qwCtrl::SetParent(qwCtrl* parent)
{
	OnChangeParent(parent);
	_parent = parent;
}

void qwCtrl::SetShed(qgWgs* shed)
{
	_shed = shed;
}

void qwCtrl::SetFont(qgFont* font)
{
	if (font && _font.Ptr() != font)
	{
		OnChangeFont(font);

		_font = font;

		SetRefresh();
	}
}

void qwCtrl::SetText(const char* str)
{
	_text_str = str;
	OnTextChanged(_text_str.Data(), _text_str.Length());
	SetRefresh();
}

void qwCtrl::SetTextL(const kwchar* uni)
{
#if _SB_WINDOWS_
	ksize_t n = k_conv_utf16_to_utf8(NULL, 0, uni, 0) + 1;
	char* psz = (char*)alloca(n*sizeof(kwchar));
	k_conv_utf16_to_utf8(psz, n, uni, 0);
#else
	ksize_t n = k_conv_ucs4_to_utf8(NULL, 0, uni, 0) + 1;
	char* psz = (char*)alloca(n*sizeof(kwchar));
	k_conv_ucs4_to_utf8(psz, n, uni, 0);
#endif

	SetText(psz);
}

void qwCtrl::BuildBound(bool clip)
{
	const tpl::Rect& offset = _parent ? _parent->GetClient() : _shed->GetBound();

	_client = _bound.Offset(offset.left, offset.top);

	if (clip)
	{
		// 클립을 하지 않을 경우
		// combo가 form/panel의 쫄따구 일 때 열리면 부모 영역을 넘어감. 클립하면 안됨
		if (_client.right > offset.right)
			_client.right = offset.right;

		if (_client.bottom > offset.bottom)
			_client.bottom = offset.bottom;
	}
}

tpl::Rect qwCtrl::CalcBound(const tpl::Rect& bound)
{
	const tpl::Rect& offset = _parent ? _parent->GetClient() : _shed->GetBound();

	return bound.Offset(offset.left, offset.top);
}

tpl::Point qwCtrl::CalcAlign(const tpl::Point& size)
{
	return CalcAlign(_text_info.align, size, _client);
}

tpl::Point qwCtrl::CalcAlign(kint align, const tpl::Point& size, const tpl::Rect& bound)
{
	kint w = bound.Width();
	kint h = bound.Height();

	return tpl::Point(
		(align&QGWALIGN_RIGHT) ? w - size.x : (align&QGWALIGN_CENTER) ? (w - size.x) / 2 : 0,
		(align&QGWALIGN_BOTTOM) ? h - size.y : (align&QGWALIGN_VCENTER) ? (h - size.y) / 2 : 0);
}

void qwCtrl::SortWidgets(kint count, qwCtrl** ctrls)
{
	tpl::HeapSort(ctrls, count, _widget::SortWidget());
}

bool qwCtrl::TestPick(kint x, kint y, bool isclient)
{
	if (_parent && isclient)
	{
		const tpl::Rect& offset = _parent->GetClient();
		x -= offset.left;
		y -= offset.top;
	}
	else
	{
#if 0
		const tpl::Rect& offset = _client;
		x -= offset.left;
		y -= offset.top;
#endif
	}

	return _bound.OnPoint(x, y);
}

void qwCtrl::BuildTextAnch()
{
	_font->SetSize(_text_info.size);
	_text_info.anch = CalcAlign(_font->CalcPoint(0, _text_str.Data()));
}

void qwCtrl::OnInitialize()
{
	_res[QGWS_NORMAL] = _shed->GetResource(QGWELM_NORMAL);
	_res[QGWS_SELECT] = _shed->GetResource(QGWELM_SELECT);
	_res[QGWS_OVER] = _shed->GetResource(QGWELM_OVER);
	_res[QGWS_DISABLE] = _shed->GetResource(QGWELM_DISABLE);
}

bool qwCtrl::OnUpdate(float adv)
{
	if (NeedRefresh())
		SetRefresh(false);

	return true;
}

void qwCtrl::OnDraw()
{
#if 0
	if (CanFill())
		_shed->DrawFillRect(_tex, _client, GetColor(), GetCoord());
#endif

	if (_ecb_draw)
		_ecb_draw(this, _client);
}


//////////////////////////////////////////////////////////////////////////
// 창고

// 임플
class qgWgsImpl : public qgWgs, public qbSevHandler
{
	KCH_AUTOCLASS(qgWgsImpl, qgWgs, "WidgetShed");

public:
	typedef tpl::VecArray<qwCtrl*> ControlArray;
	typedef tpl::HashTable<tpl::BlueStr<64>, qwEventFunc> EventHash;

private:
	ControlArray	_ctls;
	EventHash		_evht;

	qwCtrl*			_wmodal;
	qwCtrl*			_wfocus;
	qwCtrl*			_wselect;
	qwCtrl*			_wover;
	qwCtrl*			_wactive;

private:
	qgWgsImpl()
	{
		// 이벤트
		qgRdh* rdh = qgRdh::Instance;
		qgStub* stub = rdh->GetStub();
		stub->AddHandler(this);

		// 리소스
		RegisterResProp();
	}

	~qgWgsImpl()
	{
		ClearControls();

		qgRdh* rdh = qgRdh::Instance;
		qgStub* stub = rdh->GetStub();
		stub->RemoveHandler(this);
	}

public:
	//
	K_OVR void Update(float adv)
	{
		if (!_insts.enable)
			return;

		if (_wmodal)
		{
			// 모달만 업데이트
			_wmodal->OnUpdate(adv);
		}
		else
		{
			kForEach(qwCtrl* w, _ctls)
			{
				if (w->IsVisible() && !w->OnUpdate(adv))
					break;
			}
		}
	}

	//
	K_OVR void Draw()
	{
		if (!_insts.enable || !_insts.visible)
			return;

		if (_ctls.IsHave())
		{
			//
			//qgRdh* rdh = qgRdh::Instance;

			//rdh->OrthoBegin(NULL);

			// 역순으로 그림
			for (auto it = _ctls.Last();; --it)
			{
				qwCtrl* w = *it;

				if (w->IsVisible() && w != _wmodal)
					w->OnDraw();

				if (it == _ctls.First())
					break;
			}

			// 모달은 나중에
			if (_wmodal)
				_wmodal->OnDraw();

			//
			//rdh->OrthoEnd();
		}
	}

	//
	K_OVR qwCtrl* GetControl(const char* name)
	{
		k_return_value_if_fail(name, NULL);

		kForEach(qwCtrl* w, _ctls)
		{
			if (w->GetName().CompareTo(name, true) == 0)
				return w;

			if (!w->IsContainer())
				continue;

			qbWidgetContainer* ctnr = w->GetContainer();
			w = ctnr->GetControl(name);

			if (w)
				return w;
		}

		return NULL;
	}

	//
	K_OVR qwCtrl* FindControl(kint id)
	{
		kForEach(qwCtrl* w, _ctls)
		{
			if (w->GetId() == id)
				return w;

			if (!w->IsContainer())
				continue;

			qbWidgetContainer* ctnr = w->GetContainer();
			w = ctnr->FindControl(id);

			if (w)
				return w;
		}

		return NULL;
	}

	//
	K_OVR qwCtrl* GetControlNth(kint nth)
	{
		return (ksize_t)nth < (ksize_t)_ctls.Count() ? _ctls[nth] : NULL;
	}

	//
	K_OVR kint IndexControlOf(qwCtrl* cx)
	{
		k_return_value_if_fail(cx, -1);

		return _ctls.Contains(cx);
	}

	//
	K_OVR bool AddControl(qwCtrl* cx)
	{
		k_return_value_if_fail(cx, false);

		if (cx->GetShed() || cx->GetParent())
		{
			kint nth = _ctls.Contains(cx);

			if (nth >= 0)
				return false;
		}

		qgWgsImpl::UnsafeAddControl(cx);

		return true;
	}

	//
	K_OVR bool RemoveControl(qwCtrl* cx)
	{
		k_return_value_if_fail(cx, false);
		k_return_value_if_fail(cx->GetShed() == this, false);

		return qgWgsImpl::UnsafeRemoveControl(cx);
	}

	//
	K_OVR void UnsafeAddControl(qwCtrl* cx)
	{
		cx->SetDepth(++_last_depth);

		_ctls.Add(cx);
		qgWgsImpl::SortControls(cx);

		cx->Load();
	}

	//
	K_OVR bool UnsafeRemoveControl(qwCtrl* cx)
	{
		kint nth = _ctls.Contains(cx);

		if (nth < 0)
			return false;

		_ctls.RemoveAt(nth);

		cx->Unload();

		return true;
	}

	//
	K_OVR void ClearControls()
	{
		kForEach(qwCtrl* w, _ctls)
		{
			w->SetParent(NULL);
			w->Unload();
		}

		_ctls.Clear();
	}

	//
	K_OVR void SortControls(qwCtrl* primary)
	{
		// 만약 primary가 0번이면 소트하지 말자, primary가 NULL이라도 상관없음
		// 이 함수를 호출하는 곳에서는 컨트롤 1개만 조작이 들어가기 때문에...
		if (primary == *_ctls.Begin())
			return;

		//
		kint count = _ctls.Count();
		_last_depth = count;

		// 먼저 소트
		// [2013-12-18 KIM] QuickSort에 문제가 있음, 해결 될 때까진 HeapSort만 쓸것
		qwCtrl::SortWidgets(count, _ctls.Data());

		// 깊이 재정렬
		kForEach(qwCtrl* w, _ctls)
			w->SetDepth(count--);

#if 0 && _SB_DEBUG_
		char sz[260]; k_dts(NULL, 0xFF, sz, 260 - 1);
		k_mesgf("SORT %s\n", sz);

		for (kint i = 0; i < _ctls.Count(); i++)
		{
			qwCtrl* w = _ctls[i];
			kTypeInfo* n = w->GetTypeInfo();
			k_mesgf("SORT#%d [%d] %s\n", i, w->GetDepth(), n->name);
		}
		k_mesg("\n");
#endif
	}

	//
	K_OVR void SetCustomEvent(const char* name, const qwEventFunc& fn)
	{
		_evht.Set(name, fn);
	}

	//
	K_OVR bool RemoveCustomEvent(const char* name)
	{
		auto it = _evht.Get(name);

		if (it == _evht.Null())
			return false;
		else
		{
			_evht.Remove(it);
			return true;
		}
	}

	//
	K_OVR const qwEventFunc* GetCustomEvent(const char* name)
	{
		auto it = _evht.Get(name);

		return it == _evht.Null() ? NULL : &it->second;
	}

	//
	K_OVR void SetForeground(qwCtrl* w)
	{
		if (w)
		{
			w->SetVisible(true);

			w->SetDepth(++_last_depth);
			qgWgsImpl::SortControls(w);
		}

		if (_ecb_foreground_changed)
			_ecb_foreground_changed(w);
	}

	//
	K_OVR void SetFocus(qwCtrl* w)
	{
		if (_wfocus)
			_wfocus->OnChangeFocus(false);

		_wfocus = w;

		if (w)
			w->OnChangeFocus(true);
	}

	//
	K_OVR bool SetModal(qwCtrl* w)
	{
		if (!w)
		{
			if (_wmodal)
			{
				_wmodal->SetVisible(false);
				SetFocus(NULL);

				if (_ecb_modal_changed)
					_ecb_modal_changed(_wmodal, false);
			}

			_wmodal = NULL;
		}
		else if (w != _wmodal)
		{
			if (!w->IsContainer())
				return false;

			_wmodal = w;

			// 가운데 위치하게 끔
			const tpl::Point wsize = w->GetBound().Size();
			int x = (_bound.Width() - wsize.x) / 2;
			int y = (_bound.Height() - wsize.y) / 2;
			w->SetBound(x, y, wsize.x, wsize.y);

			// 처음으로
			SetForeground(w);
			SetFocus(NULL);

			if (_ecb_modal_changed)
				_ecb_modal_changed(_wmodal, true);
		}

		return true;
	}

	//
	K_OVR bool TestModal(qwCtrl* w)
	{
		return _wmodal == w;
	}

	//
	K_OVR bool OnLayout(kint width, kint height, bool maximize)
	{
		_bound.right = width;
		_bound.bottom = height;

		return true;
	}

	//
	K_OVR bool OnMouseMove(kint x, kint y, const tpl::Point& relative, kushort state)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		bool ret = true;
		qwCtrl* pick = NULL;

		// 포커스(전역) -> 모달 -> 선택 순서 대로
		if (_wfocus)
		{
			if (!_wfocus->OnMouseMove(x, y, relative, state))
				ret = false;

			if (_wfocus->TestPick(x, y))
				goto pos_skip;
		}

		if (_wmodal)
		{
			if (!_wmodal->OnMouseMove(x, y, relative, state))
				ret = false;

			if (_wmodal->TestPick(x, y))
				goto pos_skip;

			// 모달은 내부 선택 구조를 갖고 있기에 더 이상 처리하지 않음
		}
		else
		{
			if (_wselect)
			{
				if (!_wselect->OnMouseMove(x, y, relative, state))
					ret = false;
			}

			// 집기
			if (ret)
			{
				pick = PickMouse(x, y);

				if (pick)
				{
					if (_wover != pick)
						SetControl(pick, QGWS_OVER);

					if (_wselect != pick && !pick->OnMouseMove(x, y, relative, state))
						ret = false;
				}
			}
		}

pos_skip:
		// 안찍혔으면서 오버가 있었다면 해제
		if (!pick)
			SetControl(NULL, QGWS_OVER);

		return true;
	}

	//
	K_OVR bool OnMouseDown(kint x, kint y, kushort button)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		qwCtrl* pick = _wover ? _wover : PickMouse(x, y);

		if (_wfocus)
		{
			if (pick != _wfocus || !_wfocus->TestPick(x, y, true))
				SetFocus(NULL);
			else if (!_wfocus->OnMouseDown(x, y, button))
				return false;
		}

		if (_wmodal)
		{
			if (!_wmodal->TestPick(x, y, true))
				SetFocus(NULL);
			else if (!_wmodal->OnMouseDown(x, y, button))
				return false;
		}
		else if (pick)
		{
			// 액티브
			_wactive = pick;

			//
			if (button == QIM_LEFT || pick->IsContainer())
				SetControl(pick, QGWS_SELECT);

			pick->SetDepth(++_last_depth);
			qgWgsImpl::SortControls(pick);

			// 먼저 위에 부모가 쏘고, 자식도 검사
			if (!pick->OnMouseDown(x, y, button))
				return false;
		}

		return true;
	}

	//
	K_OVR bool OnMouseUp(kint x, kint y, kushort button)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		bool ret = true;

		if (_wfocus)
		{
			if (!_wfocus->OnMouseUp(x, y, button))
				ret = false;
		}
		else if (_wmodal)
		{
			if (!_wmodal->OnMouseUp(x, y, button))
				ret = false;
		}
		else
		{
			if (_wselect)
			{
				if (!_wselect->OnMouseUp(x, y, button))
					ret = false;
			}

			qwCtrl* pick = PickMouse(x, y);

			if (_wselect && _wselect == pick)
			{
				// 현재 선택과 놓인 위치가 같으면
				if (button == QIM_LEFT || pick->IsContainer())
					SetControl(NULL, QGWS_SELECT);
			}
			else
			{
				// 현재 선택과 눌린 위치가 다르다
				SetControl(NULL, QGWS_SELECT);

				if (pick)
				{
					if (!pick->OnMouseUp(x, y, button))
						ret = false;
				}
			}
		}

		return ret;
	}

	//
	K_OVR bool OnMouseDouble(kint x, kint y, kushort button)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		bool ret = true;

		if (_wfocus)
		{
			if (!_wfocus->OnMouseDouble(x, y, button))
				ret = false;
		}
		else if (_wmodal)
		{
			if (!_wmodal->OnMouseDouble(x, y, button))
				ret = false;
		}
		else if (_wover)
		{
			if (!_wover->OnMouseDouble(x, y, button))
				ret = false;
		}

		return ret;
	}

	//
	K_OVR bool OnMouseWheel(kshort delta)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		qwCtrl* pick =
			_wfocus ? _wfocus :
			_wmodal ? _wmodal :
			_wover ? _wover :
			_wselect ? _wselect : NULL;

		if (pick)
		{
			if (!pick->OnMouseWheel(delta))
				return false;
		}

		return true;
	}

	//
	K_OVR bool OnKeyDown(kbyte key, kbyte sub, kuint ch, bool repeat, kushort state)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		qwCtrl* pick =
			_wfocus ? _wfocus :
			_wmodal ? _wmodal :
			_wselect ? _wselect : _wactive;

		if (pick)
		{
			if (!pick->OnKeyDown(key, sub, ch, repeat, state))
				return false;
		}

		return true;
	}

	//
	K_OVR bool OnKeyUp(kbyte key, kbyte extend, kushort state)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		qwCtrl* pick =
			_wfocus ? _wfocus :
			_wmodal ? _wmodal :
			_wselect ? _wselect : _wactive;

		if (pick)
		{
			if (!pick->OnKeyUp(key, extend, state))
				return false;
		}

		return true;
	}

	//
	K_OVR bool OnChar(kint ch, bool repeat)
	{
		if (!_insts.enable || !_insts.visible)
			return true;

		qwCtrl* pick =
			_wfocus ? _wfocus :
			_wmodal ? _wmodal :
			_wselect ? _wselect : _wactive;

		if (pick)
		{
			if (!pick->OnChar(ch, repeat))
				return false;
		}

		return true;
	}

	//
	qwCtrl* PickMouse(kint x, kint y)
	{
		kForEach(qwCtrl*w, _ctls)
		{
			if (!w->IsAlive())
				continue;

			if (w->TestPick(x, y))
				return w;
		}

		return NULL;
	}

	//
	K_OVR void SetControl(qwCtrl* w, qgWidgetStatus status)
	{
		switch ((ksize_t)status)
		{
			case QGWS_SELECT:
				k_return_if_fail(w != _wselect);

				if (_wselect)
					_wselect->SetStatus(_wselect == _wover ? QGWS_OVER : QGWS_NORMAL);

				_wselect = w;

				if (w)
					w->SetStatus(QGWS_SELECT);

				break;

			case QGWS_OVER:
				if (_wover && _wover != _wselect)
					_wover->SetStatus(QGWS_NORMAL);

				_wover = w;

				if (w && w != _wselect)
					w->SetStatus(QGWS_OVER);

				break;
		}
	}

	// 위젯 등록용 클래스
	struct ResInfo : public qbWidgetResource
	{
		ResInfo(const tpl::ColorValue& color, const tpl::Quad& coord, bool usetex = true)
		{
			this->usetex = usetex;
			this->coord = coord;
			this->color = color;
		}

		explicit ResInfo(const tpl::ColorValue& color, bool usetex = true)
		{
			this->usetex = usetex;
			this->coord = tpl::Quad::FillCoord();
			this->color = color;
		}

		explicit ResInfo(const tpl::Quad& coord, bool usetex = true)
		{
			this->usetex = usetex;
			this->coord = coord;
			this->color = tpl::ColorValues::White;
		}

		explicit ResInfo(bool usetex = true)
		{
			this->usetex = usetex;
			this->coord = tpl::Quad::FillCoord();
			this->color = tpl::ColorValues::White;
		}
	};

	// 이건 위젯 리소스 등록용
	void RegisterResProp()
	{
		// 텍스챠!
		qgTexture* tex = _widget::CreateUiTexture();

		if (tex != NULL)
		{
			_tex[QGW_CONTROL] = tex;
			_tex[QGW_FORM] = tex;
			_tex[QGW_PANEL] = NULL;
			_tex[QGW_DIVIDE] = NULL;
			_tex[QGW_LABEL] = NULL;
			_tex[QGW_BUTTON] = NULL;
			_tex[QGW_SCROLL] = tex;
			_tex[QGW_LIST] = tex;
			_tex[QGW_COMBO] = tex;
			_tex[QGW_PROGRESS] = NULL;
			_tex[QGW_TEXTLINE] = NULL;

			tex->Unload();
		}

		// 기본
		_res.Add(ResInfo(tpl::ColorValue(32, 32, 32), true));
		_res.Add(ResInfo(tpl::ColorValue(30, 120, 220), true));
		_res.Add(ResInfo(tpl::ColorValue(64, 64, 64), true));
		_res.Add(ResInfo(tpl::ColorValues::Black, true));

		// DIVIDE
		_res.Add(ResInfo(tpl::ColorValue(182, 182, 182), false));
		_res.Add(ResInfo(tpl::ColorValue(80, 80, 80), false));
		_res.Add(ResInfo(tpl::ColorValue(182, 182, 182), false));
		_res.Add(ResInfo(tpl::ColorValue(80, 80, 80), false));

		// PROGRESS
		_res.Add(ResInfo(tpl::ColorValue(30, 120, 220), false));

		// SCROLL
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 0, 32, 32)));
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 32, 32, 32)));
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 64 + 1, 32, 64 - 2)));
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 128 + 1, 32, 64 - 2)));

		// LIST
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 192, 128, 32)));
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 224, 128, 32)));

		// COMBO
		_res.Add(ResInfo(tpl::ColorValue(32, 32, 32), false));
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 0, 32, 32, 32)));

		// TEXTLINE
		_res.Add(ResInfo(tpl::ColorValue(0, 0, 0), false));
		_res.Add(ResInfo(tpl::ColorValue(255, 255, 255, 180), false));
		_res.Add(ResInfo(tpl::ColorValue(255, 255, 255), false));
		_res.Add(ResInfo(tpl::ColorValue(127, 127, 127, 154), false));

		// PANEL
		_res.Add(ResInfo(tpl::ColorValue(32, 32, 32), true));

		// FORM
		_res.Add(ResInfo());
		_res.Add(ResInfo(tpl::Quad::ConvCoord(512, 512, 32, 0, 32, 32)));
	}
};

KIMPL_SUBCLASS_ABSTRACT(qgWgs, "AbstractWidgetShed");

//
qgWgs::qgWgs()
	: super_type()
	, _last_id(K_MAX_USHORT)
	, _last_depth(0)
	, _blend_speed(15.0f)
	, _alpha_override(0)
	, _res(QGWELM_MAX_VALUE)
{
	_BaseRoam(QGROAM_OBJ_1);

	_insts.enable = true;
	_insts.visible = true;
	_insts.input = true;

	const tpl::Point& size = qgRdh::Instance->GetStub()->GetSize();
	_bound.Set(0, 0, size.x, size.y);

	_font.InitPtr(qgFreeType::New());
}

//
qgWgs::~qgWgs()
{
}

//
void qgWgs::Update(float adv)
{
}

//
void qgWgs::Draw()
{
}

//
kint qgWgs::AddResource(const qbWidgetResource& res)
{
	_res.Add(res);

	return _res.Count() - 1;
}

//
bool qgWgs::SetResource(qgWidgetElement nth, const qbWidgetResource& res)
{
	k_return_value_if_fail((ksize_t)nth < (ksize_t)_res.Count(), false);

	_res[nth] = res;

	return true;
}

//
bool qgWgs::SetTexture(qgWidgetType type, qgTexture* tex)
{
	k_return_value_if_fail((ksize_t)type < QGW_MAX_VALUE, false);

	_tex[type] = tex;

	return true;
}

bool qgWgs::OpenTexture(qgWidgetType type, const char* filename)
{
	k_return_value_if_fail((ksize_t)type < QGW_MAX_VALUE, false);

	_tex[type].InitPtr(qgRdh::Instance->CreateTexture(filename, QGTEXF_NOMIP));

	return (bool)_tex[type];
}

//
void qgWgs::DrawAnchText(qgFont* font, const char* str, const tpl::Rect& bound, const qbWidgetTextInfo& info, kint flags)
{
	tpl::Rect bnd = bound.Deflate(info.anch.x, info.anch.y, 0, 0);

	if (_alpha_override == 0)
		font->SetColor(info.color);
	else
	{
		tpl::ColorValue cc(info.color.r, info.color.g, info.color.b, _alpha_override);
		font->SetColor(cc);
	}

	font->SetSize(info.size);
	font->TextDraw(bnd, flags, str);
}

//
void qgWgs::DrawFillRect(const tpl::Rect& bound, const qbWidgetResource& res, qgTexture* tex)
{
	qgRdh* rdh = qgRdh::Instance;

	if (_alpha_override == 0)
		rdh->DrawSprite(bound, res.color, res.usetex ? tex : NULL, &res.coord);
	else
	{
		tpl::ColorValue cc(res.color.r, res.color.g, res.color.b, _alpha_override);
		rdh->DrawSprite(bound, cc, res.usetex ? tex : NULL, &res.coord);
	}
}

//
void qgWgs::DrawFillRect(const tpl::Rect& bound, const tpl::ColorValue& color, qgTexture* tex, const tpl::Quad* coord)
{
	qgRdh* rdh = qgRdh::Instance;

	if (_alpha_override == 0)
		rdh->DrawSprite(bound, color, tex, coord);
	else
	{
		tpl::ColorValue cc(color.r, color.g, color.b, _alpha_override);
		rdh->DrawSprite(bound, cc, tex, coord);
	}
}

//
void qgWgs::DrawEmptyRect(const tpl::Rect& bound, const tpl::ColorValue& color)
{
	qgRdh* rdh = qgRdh::Instance;

	tpl::Vec3 pt[5] =
	{
		tpl::Vec3((float)bound.left, (float)bound.top, 0.0f),
		tpl::Vec3((float)bound.right, (float)bound.top, 0.0f),
		tpl::Vec3((float)bound.right, (float)bound.bottom, 0.0f),
		tpl::Vec3((float)bound.left, (float)bound.bottom, 0.0f),
		tpl::Vec3((float)bound.left, (float)bound.top, 0.0f),
	};
	tpl::ColorValue cc[5] = {color, color, color, color, color};
	rdh->DrawLines(true, true, 5, pt, cc);
}

//
void qgWgs::DrawLine(kint x1, kint y1, kint x2, kint y2, const tpl::ColorValue& color)
{
	qgRdh* rdh = qgRdh::Instance;
	tpl::Vec3 pt[2] = {tpl::Vec3((float)x1, (float)y1, 0.0f), tpl::Vec3((float)x2, (float)y2, 0.0f)};
	tpl::ColorValue cc[2] = {color, color};
	rdh->DrawLines(true, false, 2, pt, cc);
}

//
qwCtrl* qgWgs::CreateControl(qgWidgetType type, kint id, const tpl::Rect& bound, qbWidgetContainer* ctn)
{
	qwCtrl* w;

	switch ((ksize_t)type)
	{
		case QGW_DIVIDE:
			w = kobj_create(qwDivide);
			break;

		case QGW_LABEL:
			w = kobj_create(qwLabel);
			break;

		case QGW_BUTTON:
			w = kobj_create(qwButton);
			break;

		case QGW_PROGRESS:
			w = kobj_create(qwProgress);
			break;

		case QGW_SCROLL:
			w = kobj_create(qwScroll);
			break;

		case QGW_LIST:
			w = kobj_create(qwList);
			break;

		case QGW_COMBO:
			w = kobj_create(qwCombo);
			break;

		case QGW_TEXTLINE:
			w = kobj_create(qwTextLine);
			break;

		case QGW_PANEL:
			w = kobj_create(qwPanel);
			break;

		case QGW_FORM:
			w = kobj_create(qwForm);
			break;

		case QGW_RIBBON_BUTTON:
			w = kobj_create(qwRibbonButton);
			break;

		case QGW_RIBBON_COMBO:
			w = kobj_create(qwRibbonCombo);
			break;

		default:
			w = NULL;
	}

	if (w)
	{
		if (id == 0)
			id = ++_last_id;
		else if (id >= _last_id)
			_last_id = id++;
		else if (id < 0)
		{
			// id가 0보다 작으면 0으로 두고 처리하지 않는다
			// 이는 컨트롤이 다른 컨트롤 부하로 있을 때를 위한 처리
			id = 0;
		}

		w->SetId(id);
		w->SetFont(_font);
		w->SetTexture(_tex[type]);
		w->SetBound(bound);
		w->SetShed(this);
		w->OnInitialize();

		if (ctn)
			ctn->UnsafeAddControl(w);
	}

	return w;
}

//
qwCtrl* qgWgs::AddDivide(kint id, kint x, kint y, kint size)
{
	qwDivide* c = (qwDivide*)CreateControl(QGW_DIVIDE, id, tpl::Rect(x, y, x, y + size), this);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddLabel(kint id, kint x, kint y, kint w, kint h, const char* text)
{
	qwLabel* c = (qwLabel*)CreateControl(QGW_LABEL, id, tpl::Rect(x, y, x + w, y + h), this);

	if (text)
		c->SetText(text);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddButton(kint id, kint x, kint y, kint w, kint h, const char* text)
{
	qwButton* c = (qwButton*)CreateControl(QGW_BUTTON, id, tpl::Rect(x, y, x + w, y + h), this);

	if (text)
		c->SetText(text);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddProgress(kint id, kint x, kint y, kint w, kint h, kint minv, kint maxv)
{
	qwProgress* c = (qwProgress*)CreateControl(QGW_PROGRESS, id, tpl::Rect(x, y, x + w, y + h), this);

	c->SetRange(tpl::Point(minv, maxv));

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddScroll(kint id, kint x, kint y, kint h, kint size)
{
	qwScroll* c = (qwScroll*)CreateControl(QGW_SCROLL, id, tpl::Rect(x, y, x + size, y + h), this);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddList(kint id, kint x, kint y, kint w, kint h)
{
	qwList* c = (qwList*)CreateControl(QGW_LIST, id, tpl::Rect(x, y, x + w, y + h), this);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddCombo(kint id, kint x, kint y, kint w, kint h, kint droplen)
{
	qwCombo* c = (qwCombo*)CreateControl(QGW_COMBO, id, tpl::Rect(x, y, x + w, y + h), this);

	if (droplen > 0)
		c->SetDropLength(droplen);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddTextLine(kint id, kint x, kint y, kint w, kint h, const char* text)
{
	qwTextLine* c = (qwTextLine*)CreateControl(QGW_TEXTLINE, id, tpl::Rect(x, y, x + w, y + h), this);

	if (text)
		c->SetText(text);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddPanel(kint id, kint x, kint y, kint w, kint h)
{
	qwPanel* c = (qwPanel*)CreateControl(QGW_PANEL, id, tpl::Rect(x, y, x + w, y + h), this);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddForm(kint id, kint x, kint y, kint w, kint h, const char* text /*= NULL*/)
{
	qwForm* c = (qwForm*)CreateControl(QGW_FORM, id, tpl::Rect(x, y, x + w, y + h), this);

	if (text)
		c->SetText(text);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddRibbonButton(kint id, kint x, kint y, qgTexture* icon, const char* text)
{
	qwRibbonButton* c = (qwRibbonButton*)CreateControl(QGW_RIBBON_BUTTON, id, tpl::Rect(x, y, 0, 0), this);
	c->SetTexture(icon);

	if (text)
		c->SetText(text);

	c->Unload();

	return c;
}

//
qwCtrl* qgWgs::AddRibbonCombo(kint id, kint x, kint y, kint w, qgTexture* icon, kint dropline)
{
	qwRibbonCombo* c = (qwRibbonCombo*)CreateControl(QGW_RIBBON_COMBO, id, tpl::Rect(x, y, w, 0), this);
	c->SetTexture(icon);

	if (dropline > 0)
		c->SetDropLength(dropline);

	c->Unload();

	return c;
}

//
qgWgs* qgWgs::New()
{
	qgWgsImpl* shed = kobj_create(qgWgsImpl);
	return shed;
}

//
qgWgs* qgWgs::New(const char* filename)
{
	qgWgsImpl* shed = kobj_create(qgWgsImpl);
	shed->ReadFile(filename);
	return shed;
}

#endif	// _QG_ENABLE_WIDGET
